﻿using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Security.Permissions;

namespace ASBCC.Utility
{
    public enum LogonType : int
    {
        /// <summary>
        /// This logon type is intended for users who will be interactively using the computer, such as a user 
        /// being logged on by a terminal server, remote shell, or similar process. This logon type has the 
        /// additional expense of caching logon information for disconnected operation, and is therefore 
        /// inappropriate for some client/server applications, such as a mail server.
        /// </summary>
        LOGON32_LOGON_INTERACTIVE = 2,

        /// <summary>
        /// This logon type is intended for high performance servers to authenticate clear text passwords. 
        /// The LogonUser function does not cache credentials for this logon type.
        /// </summary>
        LOGON32_LOGON_NETWORK = 3,

        /// <summary>
        /// This logon type is intended for batch servers, where processes may be executing on behalf of a user 
        /// without their direct intervention; or for higher performance servers that process many clear-text 
        /// authentication attempts at a time, such as mail or web servers. The LogonUser function does not cache 
        /// credentials for this logon type.
        /// </summary>
        LOGON32_LOGON_BATCH = 4,

        /// <summary>
        /// Indicates a service-type logon. The account provided must have the service privilege enabled.
        /// </summary>
        LOGON32_LOGON_SERVICE = 5,

        /// <summary>
        /// This logon type is intended for GINA DLLs logging on users who will be interactively using the computer. 
        /// This logon type allows a unique audit record to be generated that shows when the workstation was unlocked.
        /// </summary>
        LOGON32_LOGON_UNLOCK = 7,

        /// <summary>
        /// Windows XP/2000: This logon type preserves the name and password in the authentication packages, 
        /// allowing the server to make connections to other network servers while impersonating the client. 
        /// This allows a server to accept clear text credentials from a client, call LogonUser, verify that 
        /// the user can access the system across the network, and still communicate with other servers.
        /// </summary>
        LOGON32_LOGON_NETWORK_CLEARTEXT = 8,

        /// <summary>
        /// Windows XP/2000: This logon type allows the caller to clone its current token and specify new credentials 
        /// for outbound connections. The new logon session has the same local identity, but uses different credentials 
        /// for other network connections. 
        /// This logon type is supported only by the LOGON32_PROVIDER_WINNT50 logon provider.
        /// </summary>
        LOGON32_LOGON_NEW_CREDENTIALS = 9
    };

    public enum LogonProvider : int
    {
        /// <summary>
        /// Use the standard logon provider for the system. The default security provider is NTLM. 
        /// Windows XP: The default provider is negotiate, unless you pass NULL for the domain name and 
        /// the user name is not in UPN format. In this case the default provider is NTLM.
        /// </summary>
        LOGON32_PROVIDER_DEFAULT = 0,

        /// <summary>
        /// Use the Windows NT 3.5 logon provider.
        /// </summary>
        LOGON32_PROVIDER_WINNT35 = 1,

        /// <summary>
        /// Use the NTLM logon provider.
        /// </summary>
        LOGON32_PROVIDER_WINNT40 = 2,

        /// <summary>
        /// Windows XP/2000: Use the negotiate logon provider.
        /// </summary>
        LOGON32_PROVIDER_WINNT50 = 3
    };

    class SecuUtil32
    {
        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
        int dwLogonType, int dwLogonProvider, ref IntPtr TokenHandle);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public extern static bool CloseHandle(IntPtr handle);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public extern static bool DuplicateToken(IntPtr ExistingTokenHandle,
        int SECURITY_IMPERSONATION_LEVEL, ref IntPtr DuplicateTokenHandle);
    }

    public class NetworkSecurity
    {
        public NetworkSecurity()
        {
            //
            // TODO: Add constructor logic here
            //
        }

        /// <summary>
        /// The ImpersonateUser function attempts to log a user on to the local computer. 
        /// The local computer is the computer from which ImpersonateUser was called. 
        /// You cannot use ImpersonateUser to log on to a remote computer. 
        /// You specify the user with a user name and domain, and authenticate the user with a clear-text password. 
        /// If the function succeeds, you receive a handle to a token that represents the logged-on user. 
        /// You can then use this token handle to impersonate the specified user, or in most cases, 
        /// to create a process running in the context of the specified user.
        /// </summary>
        /// <param name="strDomain">
        /// specifies the name of the domain or server whose account database contains the strLogin account.
        /// </param>
        /// <param name="strLogin">specifies the name of the user.</param>
        /// <param name="strPwd">specifies the clear-text password for the user account specified by strLogin.</param>
        /// <param name="logonType">Specifies the type of logon operation to perform.</param>
        /// <param name="logonProvider">Specifies the logon provider.</param>
        /// <example>
        /// //Add System.Security.dll
        /// //using System.Security.Principal;
        /// 
        /// string strDomain=ConfigurationSettings.AppSettings["mSALoginDomainName"];
        /// string strUser=ConfigurationSettings.AppSettings["mSALoginDomainUser"];
        /// string strPassword=ConfigurationSettings.AppSettings["mSALoginDomainPassword"];
        /// 
        /// WindowsImpersonationContext impContext = null;
        /// try
        /// {
        /// impContext = NetworkSecurity.ImpersonateUser(strDomain,strUser,strPassword,
        /// LogonType.LOGON32_LOGON_SERVICE,
        /// LogonProvider.LOGON32_PROVIDER_DEFAULT);
        /// }
        /// catch
        /// {
        /// 
        /// }
        /// 
        /// //work under this logined user
        /// 
        /// impContext.Undo();
        /// </example>
        /// <returns>
        /// </returns>
        public static WindowsImpersonationContext ImpersonateUser(string strDomain,
        string strLogin,
        string strPwd,
        LogonType logonType,
        LogonProvider logonProvider)
        {
            // Initialize tokens
            IntPtr tokenHandle = new IntPtr(0);
            IntPtr dupeTokenHandle = new IntPtr(0);
            tokenHandle = IntPtr.Zero;
            dupeTokenHandle = IntPtr.Zero;

            // If domain name was blank, assume local machine
            if (strDomain == "")
                strDomain = System.Environment.MachineName;

            try
            {
                const int SecurityImpersonation = 2;

                // Call LogonUser to obtain a handle to an access token.
                bool returnValue = SecuUtil32.LogonUser(
                strLogin,
                strDomain,
                strPwd,
                (int)logonType,
                (int)logonProvider,
                ref tokenHandle);

                // Did impersonation fail?
                if (false == returnValue)
                {
                    int ret = Marshal.GetLastWin32Error();
                    // Throw the exception show the reason why LogonUser failed
                    string strErr = String.Format("LogonUser failed with error code : {0}", ret);
                    throw new ApplicationException(strErr, null);
                }

                // Get identity before impersonation
                bool retVal = SecuUtil32.DuplicateToken(tokenHandle, SecurityImpersonation, ref dupeTokenHandle);

                // Did DuplicateToken fail?
                if (false == retVal)
                {
                    // Close existing handle
                    SecuUtil32.CloseHandle(tokenHandle);
                    // Throw the exception show the reason why DuplicateToken failed
                    throw new ApplicationException("Failed to duplicate token", null);
                }

                // Create new identity using new primary token
                // The token that is passed to the following constructor must 
                // be a primary token in order to use it for impersonation.
                WindowsIdentity newId = new WindowsIdentity(dupeTokenHandle);
                WindowsImpersonationContext impersonatedUser = newId.Impersonate();

                return impersonatedUser;
            }
            catch (Exception ex)
            {
                throw new ApplicationException(ex.Message, ex);
            }
            finally
            {
                // Close handle
                if (tokenHandle != IntPtr.Zero)
                    SecuUtil32.CloseHandle(tokenHandle);
                if (dupeTokenHandle != IntPtr.Zero)
                    SecuUtil32.CloseHandle(dupeTokenHandle);
            }
        }
    }
}
